// CyTargetLinker,
// a Cytoscape plugin to extend biological networks with regulatory interaction
//
// Copyright 2011-2013 Department of Bioinformatics - BiGCaT, Maastricht University
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cytargetlinker.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cytargetlinker.CyTargetLinker.Direction;
import cytargetlinker.Preferences;
import cytoscape.CyEdge;
import cytoscape.CyNetwork;
import cytoscape.CyNode;
import cytoscape.Cytoscape;
import cytoscape.data.CyAttributes;

/**
 * network that will be created by the plugin 
 * use user network and extend it with RINs
 * @author martina
 *
 */
public class ResultingNetwork {

	private CyNetwork network;
	private String name;
	private Map<String, CyNode> newNodes;
	private Map<String, CyEdge> newEdges;
	
	private List<CyNode> nodesOriginal;
	
	private List<Interaction> interactions;
	
	private List<giny.model.Node> hiddenNodes;
	private Map<String, Double> xPosition;
	private Map<String, Double> yPosition;
	
		
	public ResultingNetwork() {
		newNodes = new HashMap<String, CyNode>();
		newEdges = new HashMap<String, CyEdge>();
		nodesOriginal = new ArrayList<CyNode>();
		interactions = new ArrayList<Interaction>();
		
		hiddenNodes = new ArrayList<giny.model.Node>();
		xPosition = new HashMap<String, Double>();
		yPosition = new HashMap<String, Double>();
	}
	
	public void copyUserNetwork(UserNetwork userNetwork) {
		network = Cytoscape.createNetwork(userNetwork.getNetwork().nodesList(), 
				userNetwork.getNetwork().edgesList(), name);
		nodesOriginal.addAll(userNetwork.getNetwork().nodesList());
	}
	
	public void createNodeTypeAttribute() {
		CyAttributes cyNodeAttrs = Cytoscape.getNodeAttributes();
		String attribute = Preferences.getInstance().getNodeTypeAttribute();
		String groupNode = Preferences.getInstance().getGroupNodeValue();
		String networkNode = Preferences.getInstance().getNetworkNodeValue();
		String newNode = Preferences.getInstance().getNewNodeValue();
		
		@SuppressWarnings("unchecked")
		List<CyNode> list = network.nodesList();
		for (CyNode node : list) {
			// check if node has value in attribute NodeType
			String value = cyNodeAttrs.getStringAttribute(node.getIdentifier(), attribute);
			// if not = set value to NetworkNode
			if(value != null) {
				if(!value.equals(newNode)) {
					if(isGroupNode(node, cyNodeAttrs)) {
						cyNodeAttrs.setAttribute(node.getIdentifier(),  attribute, groupNode);
					} else {
						cyNodeAttrs.setAttribute(node.getIdentifier(),  attribute, networkNode);
						cyNodeAttrs.setAttribute(node.getIdentifier(),  "label", getLabel(node, cyNodeAttrs));
					}
				}
			} else {
				if(value == null || value.equals("")) {
					if(isGroupNode(node, cyNodeAttrs)) {
						cyNodeAttrs.setAttribute(node.getIdentifier(),  attribute, groupNode);
					} else {
						cyNodeAttrs.setAttribute(node.getIdentifier(),  attribute, networkNode);
						cyNodeAttrs.setAttribute(node.getIdentifier(),  "label", getLabel(node, cyNodeAttrs));
					}
				}
			}
		}
		// Inform others via property change event.
		Cytoscape.firePropertyChange(Cytoscape.ATTRIBUTES_CHANGED, null, null);
	}
	
	public void addNewEdges(List<RIN> rins, Direction direction, UserNetwork userNetwork) {
		CyAttributes cyNodeAttrs = Cytoscape.getNodeAttributes();
		CyAttributes cyEdgeAttrs = Cytoscape.getEdgeAttributes();
		for(RIN rin : rins) {
			for(Edge edge : rin.getEdges()) {
				Node source = edge.getSource();
				Node target = edge.getTarget();
				
				CyNode cySource = null;
				CyNode cyTarget = null;
				if(direction == Direction.BOTH || direction == Direction.SOURCE) {
					cySource = findMatchingCyNode(source, userNetwork);
					cyTarget = createNewNode(target, cyNodeAttrs, rin, userNetwork);
					createEgde(cySource, cyTarget, edge, rin, cyEdgeAttrs);	
				}
				if(direction == Direction.BOTH || direction == Direction.TARGET) {
					cySource = createNewNode(source, cyNodeAttrs, rin, userNetwork);
					cyTarget = findMatchingCyNode(target, userNetwork);
					createEgde(cySource, cyTarget, edge, rin, cyEdgeAttrs);
				} 
			}
		}
	}
	
	/**
	 * creates a new edge in the resulting network
	 */
	private void createEgde(CyNode source, CyNode target, Edge edge, RIN rin, CyAttributes cyEdgeAttrs) {
		if(source != null && target != null) {
			CyEdge cyEdge = createNewEdge(source.getIdentifier(), target.getIdentifier(), edge, rin, rin.getInteractionType(), cyEdgeAttrs);
			edge.setCySource(source);
			edge.setCyTarget(target);
			
			newEdges.put(cyEdge.getIdentifier(), cyEdge);
			network.addEdge(cyEdge);
			rin.getEdgesVisible().add(cyEdge);

			Interaction i = findInteraction(source, target);
			if(i == null) {
				i = new Interaction(source, target);
				interactions.add(i);
			} 
			i.getEdges().put(cyEdge, rin);
		}
	}
	
	public Interaction findInteraction(CyNode source, CyNode target) {
		for(Interaction i : interactions) {
			if(i.getSource().equals(source) && i.getTarget().equals(target)) {
				return i;
			}
		}
		return null;
	}
	
	/**
	 * creates a new edge with a specific interaction type of the regulatory
	 * interaction network
	 */
	private CyEdge createNewEdge(String sourceId, String targetId, Edge edge, RIN rin, String edgeType, CyAttributes cyEdgeAttrs) {
		CyEdge cyEdge = Cytoscape.getCyEdge(sourceId, edgeType, targetId, rin.getName());
		
		for(String str : edge.getProperties().keySet()) {
			cyEdgeAttrs.setAttribute(cyEdge.getIdentifier(), str, edge.getProperties().get(str));
		}
		cyEdgeAttrs.setAttribute(cyEdge.getIdentifier(), "interaction", rin.getName());
		return cyEdge;
	}
	
	/**
	 * creates a new node if the node is not present in the user network
	 */
	private CyNode createNewNode(Node node, CyAttributes cyNodeAttrs, RIN rin, UserNetwork userNetwork) {
		CyNode cyNode = null;

		String id = node.getId();
		for(String str : node.getIdentifiers()) {
			if(userNetwork.getNodes().containsKey(str)) {
				id = str;
				break;
			}
		}

		// check if node already exists
		if(!userNetwork.getNodes().containsKey(id)) {
			// check if node was already added as a new node
			for(String str : node.getIdentifiers()) {
				if(newNodes.containsKey(str)) {
					return newNodes.get(str);
				}
			}
			
			cyNode = Cytoscape.getCyNode(id, true);
			for(String str : node.getProperties().keySet()) {
				cyNodeAttrs.setAttribute(cyNode.getIdentifier(), str, node.getProperties().get(str));
			}
			if(node.getIdentifiers().size() > 1) {
				cyNodeAttrs.setListAttribute(cyNode.getIdentifier(), "identifiers", node.getIdentifiers());
			}
			cyNodeAttrs.setAttribute(cyNode.getIdentifier(), "label", getLabel(cyNode, cyNodeAttrs));
			cyNodeAttrs.setAttribute(cyNode.getIdentifier(), Preferences.getInstance().getNodeTypeAttribute(), Preferences.getInstance().getNewNodeValue());
			
			network.addNode(cyNode);
			
			for(String str : node.getIdentifiers()) {
				newNodes.put(str, cyNode);
			}
		} else {
			cyNode = userNetwork.getNodes().get(id);
		}
		
		return cyNode;
	}
	
	/**
	 * checks if the user network does not contain a node
	 * with one of the identifiers specified
	 */
	private CyNode findMatchingCyNode(Node node, UserNetwork userNetwork) {
		for(String id : node.getIdentifiers()) {
			if(userNetwork.getNodes().containsKey(id)) {
				return userNetwork.getNodes().get(id);
			}
		}
		return null;
	}
	
	
	/**
	 * if a node has an attribute "label" or "Label"
	 * it is used to label the node
	 * otherwise the user would see the often unreadable identifiers
	 */
	private String getLabel(CyNode node, CyAttributes cyNodeAttrs) {
		String label = cyNodeAttrs.getStringAttribute(node.getIdentifier(), "label");
		if(label == null) {
			label = cyNodeAttrs.getStringAttribute(node.getIdentifier(), "Label");
			if(label == null) {
				String name = cyNodeAttrs.getStringAttribute(node.getIdentifier(), "canonicalName");
				if(name != null && !name.equals("")) {
					label = name;
				} else {
					label = node.getIdentifier();
				}
			}
		}
		return label;
	}
	
	/**
	 * checks if a node is a GPML group node
	 * specific fix for the GPML plugin
	 */
	private boolean isGroupNode(CyNode node, CyAttributes cyNodeAttrs) {
		String groupStyle = cyNodeAttrs.getStringAttribute(node.getIdentifier(), "GroupStyle");
		if(groupStyle != null && (groupStyle.equals("Group") || groupStyle.equals("Complex"))) {
			return true;
		}
		String anchorStyle = cyNodeAttrs.getStringAttribute(node.getIdentifier(), "StartLineType");
		if(anchorStyle != null) {
			return true;
		}
		return false;
	}
	
	
	/**
	 * checks if network name is already given
	 */
	private void checkName() {
		Set<CyNetwork> networks = Cytoscape.getNetworkSet();
		for(CyNetwork net : networks) {
			if(net.getTitle().equals(name)) {
				setName(name + "_1");
				checkName();
			}
		}
	}

	// SETTERS & GETTERS
	
	public CyNetwork getNetwork() {
		return network;
	}
	public void setNetwork(CyNetwork network) {
		this.network = network;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
		checkName();
	}
	public Map<String, CyNode> getNewNodes() {
		return newNodes;
	}
	public void setNewNodes(Map<String, CyNode> newNodes) {
		this.newNodes = newNodes;
	}
	public Map<String, CyEdge> getNewEdges() {
		return newEdges;
	}
	public void setNewEdges(Map<String, CyEdge> newEdges) {
		this.newEdges = newEdges;
	}
	public List<CyNode> getNodesOriginal() {
		return nodesOriginal;
	}
	public void setNodesOriginal(List<CyNode> nodesOriginal) {
		this.nodesOriginal = nodesOriginal;
	}
	public List<Interaction> getInteractions() {
		return interactions;
	}
	public void setInteractions(List<Interaction> interactions) {
		this.interactions = interactions;
	}
	public List<giny.model.Node> getHiddenNodes() {
		return hiddenNodes;
	}
	public void setHiddenNodes(List<giny.model.Node> hiddenNodes) {
		this.hiddenNodes = hiddenNodes;
	}
	public Map<String, Double> getxPosition() {
		return xPosition;
	}
	public void setxPosition(Map<String, Double> xPosition) {
		this.xPosition = xPosition;
	}
	public Map<String, Double> getyPosition() {
		return yPosition;
	}
	public void setyPosition(Map<String, Double> yPosition) {
		this.yPosition = yPosition;
	}
}